package tool
 
object TreePrinter {
	import parser.Trees._
	import analyzer.Symbols._

	/** TreePrinter(tree) will produce the same result as before. */
	def apply: (Tree=>String) = apply(false)_
	  
	/** TreePrinter.withSymbolIDs(tree) will print the tree with the IDs. */
	def withSymbolIDs: (Tree=>String) = apply(true)_

	  
	var tabVal: Int = 0

	def indent: String = {
		var s: String = ""
		for(i <- 0 to tabVal -1)
			s += ' '
		return s
	}

	def increaseIndent = tabVal += 4

	def decreaseIndent = tabVal -= 4

	def createIndentedBlock(withSymbolIDs: Boolean)(t: Tree): String = t match {
	    case Block(s) => apply(withSymbolIDs)(t)
	    case _ => {
	    	increaseIndent
		    var s = "\n"+indent + apply(withSymbolIDs)(t)
		    decreaseIndent
		    return s+"\n"
	    }
	}
 
    def createBlock(withSymbolIDs: Boolean)(stmts: List[StatTree]) = {
        var s = beginIndentedBlock
		s += "\n" + createIndentedList(withSymbolIDs)(stmts)
		s += endIndentedBlock
        s
    }
    
    def beginIndentedBlock = {
        var s = "{"
	    increaseIndent
        s
    }
    
    def endIndentedBlock = {
        decreaseIndent
        indent + "}\n"
    }

	def createIndentedList(withSymbolIDs: Boolean)(t: List[Tree]): String = t match {
	    case Nil => ""
	    case x::xs => indent + apply(withSymbolIDs)(x) + createIndentedList(withSymbolIDs)(xs)
	}

	def createComaList(withSymbolIDs: Boolean)(t: List[Tree]): String = t match{
		case x :: Nil => apply(withSymbolIDs)(x)
		case x :: xs => apply(withSymbolIDs)(x) + ", " + createComaList(withSymbolIDs)(xs)
		case Nil => ""
	}
	
	def createClassBody(withSymbolIDs: Boolean)(varDecls: List[Tree], metDecls: List[Tree]): String = {
		var s = beginIndentedBlock
		s += "\n"+createIndentedList(withSymbolIDs)(varDecls)
		s += "\n"+createIndentedList(withSymbolIDs)(metDecls)
		s += endIndentedBlock
		s
	}

	def binaryOp(withSymbolIDs: Boolean)(lhs: ExprTree, rhs: ExprTree, op: String): String = "( " + apply(withSymbolIDs)(lhs) + op + apply(withSymbolIDs)(rhs) + " )"

	def apply(withSymbolIDs: Boolean)(t: Tree): String = t match {
		case Block(s) => createBlock(withSymbolIDs)(s)
		case While(test,stat) => "\n" + indent + "while(" + apply(withSymbolIDs)(test) + ") " + createIndentedBlock(withSymbolIDs)(stat) + "\n"
		case IfElse(test, stat, or) => "\n"+ indent + "if(" + apply(withSymbolIDs)(test) + ") " + createIndentedBlock(withSymbolIDs)(stat) + indent + "else " + createIndentedBlock(withSymbolIDs)(or) + "\n"
		case If(test, stat) => "\n"+ indent + "if(" + apply(withSymbolIDs)(test) + ") " + createIndentedBlock(withSymbolIDs)(stat) + "\n"
		case Println(e) => "println("  + apply(withSymbolIDs)(e) + ");\n"
		case Assign(id, e) => apply(withSymbolIDs)(id) + " = " + apply(withSymbolIDs)(e) + ";\n"
		case AssignArrayCell(id, idx, value) => apply(withSymbolIDs)(id) + "[" + apply(withSymbolIDs)(idx) + "] = " + apply(withSymbolIDs)(value) + ";\n"
		case Plus(lhs, rhs) => binaryOp(withSymbolIDs)(lhs, rhs, " + ")
		case Minus(lhs, rhs) =>  binaryOp(withSymbolIDs)(lhs, rhs, " - ")
		case Times(lhs, rhs) => binaryOp(withSymbolIDs)(lhs, rhs, " * ")
		case Div(lhs, rhs) => binaryOp(withSymbolIDs)(lhs, rhs, " / ")
		case And(lhs, rhs) => binaryOp(withSymbolIDs)(lhs, rhs, " && ")
		case Or(lhs, rhs) => binaryOp(withSymbolIDs)(lhs, rhs, " || ")
		case Equals(lhs, rhs) => binaryOp(withSymbolIDs)(lhs, rhs, " == ")
		case LessThan(lhs, rhs) => binaryOp(withSymbolIDs)(lhs, rhs, " < ")
		case Not(e) =>  "!(" + apply(withSymbolIDs)(e) + ")"
		case IntType =>  "Int"
		case BoolType =>  "Boolean"
		case StringType => "String"
		case ArrayType => "Int[]"
        case TryCatchBlock(body, catches, Some(f)) => apply(withSymbolIDs)(TryCatchBlock(body, catches, None)) + apply(withSymbolIDs)(f)
        case TryCatchBlock(body, catches, None) => "\n" + indent + "try " + createBlock(withSymbolIDs)(body.stats) + createIndentedList(withSymbolIDs)(catches)
        case Catch(exceptionId,exceptionType,body) => "catch (" + apply(withSymbolIDs)(exceptionId) + ":" + apply(withSymbolIDs)(exceptionType) + ") " + createBlock(withSymbolIDs)(body.stats)
        //case ExceptionCreation(exceptionType,msg) => "throw new " + apply(withSymbolIDs)(exceptionType) + "(" + apply(withSymbolIDs)(msg) + ");\n"
		case Throw(expr) => "throw " + apply(withSymbolIDs)(expr) + ";\n"
        case Finally(body) => indent + "finally " + createBlock(withSymbolIDs)(body.stats)
        
		case Program(main, classes) => apply(withSymbolIDs)(main) + "\n" + createIndentedList(withSymbolIDs)(classes)
		case obj @ MainObject(id, stat) => {
			var s: String = "object " 
			if(withSymbolIDs){
				try{
					s += obj.getSymbol.name + "#" + obj.getSymbol.id
				}catch{
					case e: java.lang.RuntimeException => s += id.value + "#??"
				}
			}else s += id.value
      
			s += " " + beginIndentedBlock
			s += "\n" + indent + "def main():Unit = "
			s += createBlock(withSymbolIDs)(stat::Nil)
			s += endIndentedBlock
			s
		}
		case s @ ClassDecl(id, varDecls, metDecls) => {
			var t: String = "class "
			if(withSymbolIDs){
				try{
					t += s.getSymbol.name + "#" + s.getSymbol.id
				}catch{
					case e: java.lang.RuntimeException => t += id.value + "#??"
				}
			}else t += id.value
			t += " " + createClassBody(withSymbolIDs)(varDecls,metDecls)
			t
		}
		case s @ ClassWithExtends(id, superClass, varDecls, metDecls) => {
			var t: String = "class "
			if(withSymbolIDs){
				try{
					t += s.getSymbol.name + "#" + s.getSymbol.id
				}catch{
					case e: java.lang.RuntimeException => t += id.value + "#??"
				}
			}else t += id.value
			t += " extends " + apply(withSymbolIDs)(superClass) + " "
			t += createClassBody(withSymbolIDs)(varDecls,metDecls)
			t
		}
		case sym @ VarDecl(id, t) =>
			var s: String = "var " 
			if(withSymbolIDs){
				try {
					s += id.getSymbol.name + "#" + id.getSymbol.id 
				}catch {
					case e: java.lang.RuntimeException => s += id.value + "#??"
				}
			}else s += id.value
			s += " : " 
			s += apply(withSymbolIDs)(t) + ";\n"
			s
        case s @ MethDecl(id, args, returnType, throws, varDecls, Block(l), ret) => {
			var t: String = "def "
			if(withSymbolIDs){
				try {
					t += s.getSymbol.name + "#" + s.getSymbol.id 
				}catch {
					case e: java.lang.RuntimeException => id.value + "#??"
				}
			}else t += id.value
			t += "(" + createComaList(withSymbolIDs)(args)
			t += "):" + apply(withSymbolIDs)(returnType)
   
            t +=  throws.map(g => apply(withSymbolIDs)(g)).mkString(" throws ", ", ", "")
   
			t += " = "
            t += beginIndentedBlock + "\n"
			t += createIndentedList(withSymbolIDs)(varDecls) + "\n"
			t += createIndentedList(withSymbolIDs)(l)
			t += indent + "return " + apply(withSymbolIDs)(ret) + ";\n"
			t += endIndentedBlock
            t + "\n"
		}
		case s @ Argument(id, t) => 
			if(withSymbolIDs)
				try{
					id.getSymbol.name + "#" + id.getSymbol.id + " : " + apply(withSymbolIDs)(t)
				}catch{
				   	case e: java.lang.RuntimeException => id.value + "#??" + " : " + apply(withSymbolIDs)(t)
				}
			else apply(withSymbolIDs)(id) + " : " + apply(withSymbolIDs)(t)

		case ArrayCell(tab, idx) => apply(withSymbolIDs)(tab) + "[" + apply(withSymbolIDs)(idx) + "]"
		case Length(tab) => apply(withSymbolIDs)(tab) + ".length"
		case MethodCall(instanceId, method, args) => 
				"(" + apply(withSymbolIDs)(instanceId) + "." + apply(withSymbolIDs)(method) + "(" + createComaList(withSymbolIDs)(args) + "))"
		case IntLit(v) => v.toString
		case StringLit(s) => "\"" + s + "\""
		case BoolLit(b) => b.toString
		case ArrayCreation(size) => "new Int[" + apply(withSymbolIDs)(size) + "]"
		case Instanciation(t) => "(new " + apply(withSymbolIDs)(t) + "())"
		
		case id @ Identifier(value) => 
			if(withSymbolIDs && !value.equals("this")){
				try {
					id.getSymbol.name + "#" + id.getSymbol.id 
				}catch {
					case e: java.lang.RuntimeException => value + "#??"
				}
			}else value
	}   
}
